package dgdb2d;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.nio.channels.FileLock;
import java.util.UUID;

import org.jdom2.Document;
import org.jdom2.output.XMLOutputter;

import com.tinkerpop.blueprints.pgm.Edge;
import com.tinkerpop.blueprints.pgm.Vertex;

/**
 * Klasa opisujaca krawedz grafu. Implementowany jest interfejs Edge standardu Blueprints.
 * 
 * @author Dominik Zielinski, Patryk Chaber
 * @version 0.42
 * 
 */
public class GDBEdge extends GDBElement implements Edge, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -3031810706975949658L;
	/**
	 * Nazwa katalogu, w ktorym przechowywane sa pliki.
	 */
	static final String pathPrefix = "Edges";

	/**
	 * Konstruktor z jednym parametrem, wykorzystywany jedynie przy przesylaniu indeksow do nowo podlaczonego serwera w
	 * ramach metody updateThisServer
	 * 
	 * @param id
	 *            Identyfikator krawedzi
	 */
	public GDBEdge(String id) {
		super(id);
	}

	/**
	 * Konstruktor generujacy identyfikator elementu.
	 * 
	 * @param outVertex
	 *            Wierzcholek z ktorego wychodzi krawedz.
	 * @param inVertex
	 *            Wierzcholek do ktorego wchodzi krawedz.
	 * @param label
	 *            Etykieta krawedzi.
	 */
	public GDBEdge(GDBVertex outVertex, GDBVertex inVertex, String label) {
		this(UUID.randomUUID().toString(), outVertex, inVertex, label);
	}

	/**
	 * Konstruktor z zadanym identyfikatorem elementu.
	 * 
	 * @param id
	 *            Wybrane ID.
	 * @param outVertex
	 *            Wierzcholek z ktorego wychodzi krawedz.
	 * @param inVertex
	 *            Wierzcholek do ktorego wchodzi krawedz.
	 * @param label
	 *            Etykieta krawedzi.
	 */
	public GDBEdge(String id, GDBVertex outVertex, GDBVertex inVertex, String label) {
		super(id);
		// ustawiamy etykiete
		setProperty("label", label);
		// ustawiamy wierzcholki wejsciowy i wyjsciowy
		setProperty("outvertex", outVertex.getId().toString());
		setProperty("invertex", inVertex.getId().toString());
		// zapisujemy plik
		save();

	}

	/**
	 * Konstruktor tworzacy obiekt na podstawie danych zawartych w drzewie XML.
	 * 
	 * @param root
	 *            Korzen drzewa XML.
	 */
	public GDBEdge(org.jdom2.Element root) {
		// GDBEdge oparty jest tylko o wlasciwosci, wiec sterowanie przekazywane jest do GDBElement ktory wczytuje
		// wlasciwosci
		super(root);
	}

	/**
	 * Pomocnicza metoda zwracajaca obiekt wierzcholka wejsciowego lub wyjsciowego krawedzi.
	 * 
	 * @param key
	 *            Identyfikator wierzcholka ("outvertex" lub "invertex").
	 * @return Odpowiedni obiekt wierzcholka zwiazanego z krawedzia.
	 */
	private Vertex getVertex(String key) {
		if (!key.equals("outvertex") && !key.equals("invertex"))
			return null;
		// pobieranie ID z properties
		String id = (String) getProperty(key); // UUID.fromString(
		// przypadek jak jest pusty
		if (id == null)
			return null;
		// pobranie przez GraphDataBase
		return GraphDataBase.getInstance().getVertex(id);
	}

	/**
	 * 
	 * @see com.tinkerpop.blueprints.pgm.Edge#getOutVertex()
	 */
	@Override
	public Vertex getOutVertex() {
		return getVertex("outvertex");
	}

	/**
	 * 
	 * @see com.tinkerpop.blueprints.pgm.Edge#getInVertex()
	 */
	@Override
	public Vertex getInVertex() {
		return getVertex("invertex");
	}

	/**
	 * 
	 * @see com.tinkerpop.blueprints.pgm.Edge#getLabel()
	 */
	@Override
	public String getLabel() {
		return getProperty("label").toString();
	}

	/**
	 * 
	 * @see com.tinkerpop.blueprints.pgm.Element#setProperty(java.lang.String, java.lang.Object)
	 */
	@Override
	public void setProperty(String key, Object value) {
		// sprawdzamy czy przypadkiem ktos nie chce zrobic wjazdu na wierzcholki
		if (!key.equals("invertex") && !key.equals("outvertex"))
			super.setProperty(key, value);
		// przypadek w ktorym zmieniamy wierzcholek wejsciowy/wyjsciowy
		else {
			// zakladam, ze przez value przekazywany jest id wezla w formie stringa
			// sprawdzic trzeba czy wierzcholek o takim id w ogole istnieje
			GDBVertex newVertex = (GDBVertex) GraphDataBase.getInstance().getVertex(value.toString());
			// sprawdzamy czy w ogole istnieje wpis dotyczacy odpowiedniego wierzcholka
			GDBVertex oldVertex;
			if (newVertex != null) {
				// postepowanie jest takie samo
				// 1. powiedz obecnemu wezlowi we/wy ze konczysz wspolprace <-- JEZELI ISTNIEJE WPIS GO DOTYCZACY
				// 2. powiedz nowemu wezlowi we/wy ze zaczynasz wspolprace
				// 3. zaktualizuj properties
				if (getProperty(key) != null)
					oldVertex = (GDBVertex) GraphDataBase.getInstance().getVertex(getProperty(key).toString());
				else
					oldVertex = null;
				if (key.equals("invertex")) {
					if (oldVertex != null)
						GraphDataBase.getInstance().removeRemoteFromVertexInEdge(oldVertex.getId(), this.getId(), true);
					// oldVertex.removeInEdge((String) getId());
					GraphDataBase.getInstance().addRemoteToVertexInEdge(newVertex.getId(), this.getId(),
							this.getLabel(), true);
					// newVertex.addInEdge((String)this.getId(),this.getLabel());
				} else if (key.equals("outvertex")) {
					if (oldVertex != null)
						GraphDataBase.getInstance()
								.removeRemoteFromVertexOutEdge(oldVertex.getId(), this.getId(), true);
					// oldVertex.removeOutEdge((String) getId());
					GraphDataBase.getInstance().addRemoteToVertexOutEdge(newVertex.getId(), this.getId(),
							this.getLabel(), true);
					// newVertex.addOutEdge((String)this.getId(),this.getLabel());
				}
				super.setProperty(key, value);
			}
		}
	}

	@Override
	public String toString(){
		String r = super.toString();
		r += "["+getProperty("outvertex")+"][-"+getLabel()+"->]["+getProperty("invertex")+"]";
		return r;
	}
	
	/**
	 * 
	 * @see dgdb2d.GDBElement#save(java.lang.String)
	 */
	@Override
	protected void save() {
		try {
			// utworz nowe drzewo XML
			org.jdom2.Element root = new org.jdom2.Element("GDBEdge");
			// zapisz ID krawedzi
			root.setAttribute("id", (String) getId());
			// zapisz wlasciwosci krawedzi (w tym etykiete i info o zwiazanych wierzcholkach)
			root.addContent(getPropertiesAsElement());
			// utworz nowy dokument XML
			Document document = new Document(root);
			// otworz plik o odpowiedniej nazwie, w odpowiednim katalogu
			FileOutputStream fos = new FileOutputStream(pathPrefix + File.separator + getId().toString());
			// jak pliku nie ma to plik utworz
			// ponoc ta metoda sprawdza sama czy plik istnieje
			FileLock fl;
			fl = fos.getChannel().lock(0, Long.MAX_VALUE, false);
			// przepisz dokument XML do pliku
			new XMLOutputter().output(document, fos);
			fl.release();
			fos.close();
		} catch (IOException e) {
			// ignorujemy
			System.out.print("Failed to save " + getId().toString());
		}
	}

}
